﻿using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using ControleRepresentante.Logic.Models;
using ControleRepresentante.Logic.Repositories;
using ControleRepresentante.Logic.Statistics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using WebSite.Controllers;
using WebSite.ViewModels;
using WebSite.ViewModels.DetailsViewModels;
using WebSite.ViewModels.IndexViewModels;

namespace ControleRepresentante.UnitTests.Controllers
{
    [TestClass]
    public abstract class CRUDControllerTestScenarioBase<TController, TModel, TRepository, TStatistics> : TestScenario
        where TController : CRUDControllerBase<TRepository, TModel, TStatistics>
        where TModel : ModelBase
        where TRepository : class, IRepository<TModel, TStatistics>
        where TStatistics : IStatistics
    {
        protected abstract TController CreateController(bool isAdmin = true);
        protected abstract TModel CreateModel();
        protected abstract ActionResult Create(TController controller);
        protected abstract void VerifyModel(TModel model);
        protected abstract ActionResult CreateEmptyFields(TController controller);
        protected abstract void VerifyModelEmptyFields(TModel model);
        protected abstract ActionResult CreateDuplicatedModel(TController controller, TModel model);
        protected abstract void VerifyDuplicatedModel(TModel originalModel, TModel duplicatedModel);
        protected abstract ActionResult Edit(TController controller);
        protected abstract ActionResult EditEmptyFields(TController controller);
        protected abstract ActionResult EditDuplicatedModel(TController controller, TModel modelToDuplicate);
        protected virtual bool ShouldRunTestCreateDuplicatedField { get { return true; } }
        protected virtual bool ShouldRunTestCreateEmptyFields { get { return true; } }
        protected virtual bool ShouldRunTestEditDuplicatedFields { get { return true; } }
        protected virtual bool ShouldRunTestEditEmptyFields { get { return true; } }
        protected virtual bool ShouldTestIndexEmpty { get { return true; } }
        protected virtual object[] GetRepositoryParameters() { return new object[0]; }

        [TestMethod]
        [ExpectedException(typeof(RestrictedAreaException))]
        public void TestNotAdmin()
        {
            TController controller = CreateController(false);
            controller.Index();
        }

        #region Index

        [TestMethod]
        public void TestIndexEmpty()
        {
            if (!ShouldTestIndexEmpty) return;

            TController controller = CreateController();
            ActionResult actionResult = controller.Index();
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(IndexViewModel));
            IndexViewModel indexViewModel = (IndexViewModel) viewResult.Model;
            IEnumerable<dynamic> models = indexViewModel.Models;
            Assert.IsFalse(models.Any());
        }

        [TestMethod]
        public void TestIndexNotEmpty()
        {
            TController controller = CreateController();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>(GetRepositoryParameters());
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            TModel model1 = CreateModel();
            repository.AddModel(model1);
            TModel model2 = CreateModel();
            repository.AddModel(model2);
            ActionResult actionResult = controller.Index();
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(IndexViewModel));
            IndexViewModel indexViewModel = (IndexViewModel) viewResult.Model;
            IEnumerable<dynamic> models = indexViewModel.Models;
            Assert.AreEqual(countBefore + 2, models.Count());
            Assert.IsTrue(models.Any(x => x.Id == model1.Id));
            Assert.IsTrue(models.Any(x => x.Id == model2.Id));
        }

        #endregion

        #region Create

        [TestMethod]
        public void TestCreate()
        {
            TController controller = CreateController();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>(GetRepositoryParameters());
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            controller.Create();
            ActionResult actionResult = Create(controller);
            Assert.AreEqual(countBefore + 1, repository.GetModels(out statistics).Count());
            TModel model = repository.GetModels(out statistics).Last();
            VerifyModel(model);
            Assert.IsInstanceOfType(actionResult, typeof(RedirectToRouteResult));
            RedirectToRouteResult redirectToRouteResult = (RedirectToRouteResult)actionResult;
            Assert.AreEqual("Details", redirectToRouteResult.RouteValues["action"]);
            Assert.AreEqual(model.Id, redirectToRouteResult.RouteValues["id"]);
        }

        [TestMethod]
        public void TestCreateEmptyFields()
        {
            if (!ShouldRunTestCreateEmptyFields) return;

            TController controller = CreateController();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>();
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            controller.Create();
            ActionResult actionResult = CreateEmptyFields(controller);
            Assert.AreEqual(countBefore, repository.GetModels(out statistics).Count());
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(CRUDViewModel));
            CRUDViewModel crudViewModel = (CRUDViewModel)viewResult.Model;
            Assert.IsInstanceOfType(crudViewModel.Model, typeof(TModel));
            TModel model = (TModel)crudViewModel.Model;
            VerifyModelEmptyFields(model);
        }

        [TestMethod]
        public void TestCreateDuplicatedField()
        {
            if (!ShouldRunTestCreateDuplicatedField) return;
            TController controller = CreateController();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>();
            TModel model = CreateModel();
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            repository.AddModel(model);
            Assert.AreEqual(countBefore + 1, repository.GetModels(out statistics).Count());
            controller.Create();
            ActionResult actionResult = CreateDuplicatedModel(controller, model);
            Assert.AreEqual(countBefore + 1, repository.GetModels(out statistics).Count());
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(CRUDViewModel));
            CRUDViewModel crudViewModel = (CRUDViewModel)viewResult.Model;
            Assert.IsInstanceOfType(crudViewModel.Model, typeof(TModel));
            TModel duplicatedModel = (TModel)crudViewModel.Model;
            VerifyDuplicatedModel(model, duplicatedModel);
        }

        #endregion

        #region Edit

        [TestMethod]
        public void TestEdit()
        {
            TController controller = CreateController();
            TModel model = CreateModel();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>(GetRepositoryParameters());
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            repository.AddModel(model);
            Assert.AreEqual(countBefore + 1, repository.GetModels(out statistics).Count());
            controller.Edit(model.Id);
            ActionResult actionResult = Edit(controller);
            model = repository.GetModelById(model.Id);
            VerifyModel(model);
            Assert.IsInstanceOfType(actionResult, typeof(RedirectToRouteResult));
            RedirectToRouteResult redirectToRouteResult = (RedirectToRouteResult)actionResult;
            Assert.AreEqual("Details", redirectToRouteResult.RouteValues["action"]);
            Assert.AreEqual(model.Id, redirectToRouteResult.RouteValues["id"]);
        }

        [TestMethod]
        public void TestEditEmptyFields()
        {
            if (!ShouldRunTestEditEmptyFields) return;

            TController controller = CreateController();
            TModel model = CreateModel();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>();
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            repository.AddModel(model);
            Assert.AreEqual(countBefore + 1, repository.GetModels(out statistics).Count());
            controller.Edit(model.Id);
            ActionResult actionResult = EditEmptyFields(controller);
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(CRUDViewModel));
            CRUDViewModel editViewModel = (CRUDViewModel)viewResult.Model;
            Assert.IsInstanceOfType(editViewModel.Model, typeof(TModel));
            TModel viewModelModel = (TModel)editViewModel.Model;
            Assert.AreEqual(model.Id, viewModelModel.Id);
            model = repository.GetModelById(model.Id);
            VerifyModelEmptyFields(model);
        }

        [TestMethod]
        public void TestEditDuplicatedFields()
        {
            if (!ShouldRunTestEditDuplicatedFields) return;

            TController controller = CreateController();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>();
            TStatistics statistics;
            int countBefore = repository.GetModels(out statistics).Count();
            TModel modelToBeEdited = CreateModel();
            repository.AddModel(modelToBeEdited);
            TModel model = CreateModel();
            repository.AddModel(model);
            Assert.AreEqual(countBefore + 2, repository.GetModels(out statistics).Count());
            controller.Edit(modelToBeEdited.Id);
            ActionResult actionResult = EditDuplicatedModel(controller, model);
            Assert.IsInstanceOfType(actionResult, typeof(ViewResult));
            ViewResult viewResult = (ViewResult)actionResult;
            Assert.IsInstanceOfType(viewResult.Model, typeof(CRUDViewModel));
            CRUDViewModel editViewModel = (CRUDViewModel)viewResult.Model;
            Assert.IsInstanceOfType(editViewModel.Model, typeof(TModel));
            TModel viewModelModel = (TModel)editViewModel.Model;
            Assert.AreEqual(modelToBeEdited.Id, viewModelModel.Id);
            VerifyDuplicatedModel(model, viewModelModel);
        }

        #endregion

        #region Details

        [TestMethod]
        public void TestDetails()
        {
            TController controller = CreateController();
            TModel model = CreateModel();
            TRepository repository = RepositoryProvider.GetRepository<TRepository>(GetRepositoryParameters());
            repository.AddModel(model);
            ViewResult viewResult = controller.Details(model.Id);
            Assert.IsInstanceOfType(viewResult.Model, typeof(DetailsViewModel));
            DetailsViewModel detailsViewModel = (DetailsViewModel) viewResult.Model;
            Assert.AreEqual(model, detailsViewModel.Model);
        }

        #endregion
    }
}